﻿using EnvDTE;
using Sasa.QualityTools.CodeAnalysis.Common;
using Sasa.QualityTools.CodeAnalysis.Core.VisualStudio;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
using VSLangProj;
using System.Reflection;

namespace Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.VisualStudio
{
    public class Project : IProject
    {
        protected EnvDTE.Project project;

        private List<string> referenceAssemblyPaths;


        public string Name
        {
            get
            {
                return project.Name;
            }
        }

        public string ProjectPath
        {
            get
            {
                return project.FullName;
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1031")]
        public virtual string TargetDir
        {
            get
            {
                try
                {
                    var directory = project.Properties.Item("FullPath").Value.ToString();
                    var outputPath = project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString();
                    return Path.GetFullPath(Path.Combine(directory, outputPath));
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, "Failed to get the directory that is generated assembly of '{0}' project.", project.Name);
                    return null;
                }
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1031")]
        public virtual string AssemblyPath
        {
            get
            {
                try
                {
                    var outputFileName = project.Properties.Item("OutputFileName").Value.ToString();
                    return Path.Combine(TargetDir, outputFileName);
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, "Failed to get assembly path of '{0}' project.", project.Name);
                    return null;
                }
            }
        }

        public IEnumerable<string> ReferenceAssemblyPaths
        {
            get
            {
                if (referenceAssemblyPaths == null)
                {
                    referenceAssemblyPaths = new List<string>();
                    var vsp = project.Object as VSProject;
                    if (vsp != null)
                    {
                        foreach (Reference reference in vsp.References)
                        {
                            referenceAssemblyPaths.Add(reference.Path);
                        }
                    }
                }
                return referenceAssemblyPaths;
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1031")]
        private static string GetProperties(EnvDTE.Properties props)
        {
            var builder = new System.Text.StringBuilder();
            foreach (Property p in props)
            {
                try
                {
                    builder.AppendLine(p.Name + "=" + p.Value);
                }
                catch (Exception ex)
                {
                    builder.AppendLine(ex.ToString());
                }
            }
            return builder.ToString();
        }

        public string ConfigurationPath
        {
            get
            {
                return Path.Combine(Path.GetDirectoryName(project.FullName),
                    Core.Constants.CodeAnalysisConfigurationDirName,
                    Core.Constants.CodeAnalysisConfigurationFileName);
            }
        }

        public virtual bool IsSupportedCodeAnalysis
        {
            get
            {
                return true;
            }
        }

        public virtual string NotSupportedReason
        {
            get
            {
                return null;
            }
        }

        public virtual IEnumerable<string> DependencyDirectories
        {
            get
            {
                return new List<string>();
            }
        }

        public IEnumerable<IProjectItem> Items
        {
            get
            {
                return (project.ProjectItems != null) ?
                    project.ProjectItems.OfType<EnvDTE.ProjectItem>().Select(o => new ProjectItem(o)) :
                    new List<ProjectItem>();
            }
        }

        public ISolution Solution
        {
            get
            {
                return new Solution(project.DTE.Solution);
            }
        }


        protected Project(EnvDTE.Project project)
        {
            if (project == null)
            {
                throw new ArgumentNullException("project");
            }

            this.project = project;
        }


        public static IProject CreateInstance(EnvDTE.Project project)
        {
            if (project == null)
            {
                throw new ArgumentNullException("project");
            }

            if (String.Equals(project.Kind, WebSiteProject.Guid, StringComparison.OrdinalIgnoreCase))
            {
                return new WebSiteProject(project);
            }
            else if (String.Equals(project.Kind, AzureProject.Guid, StringComparison.OrdinalIgnoreCase))
            {
                return new AzureProject(project);
            }
            else if (String.Equals(project.Kind, DatabaseProject.Guid, StringComparison.OrdinalIgnoreCase))
            {
                return new DatabaseProject(project);
            }
            else if (String.Equals(project.Kind, SetupProject.Guid, StringComparison.OrdinalIgnoreCase))
            {
                return new SetupProject(project);
            }

            var extenderNames = project.ExtenderNames as object[];
            if (extenderNames != null)
            {
                foreach (var extenderName in extenderNames)
                {
                    var name = Convert.ToString(extenderName, CultureInfo.InvariantCulture);
                    if (name == "SilverlightProject")
                    {
                        return new SilverlightProject(project);
                    }
                }
            }

            if (project.CodeModel != null && project.CodeModel.Language == CodeModelLanguageConstants.vsCMLanguageVC)
            {
                return new CppProject(project);
            }

            var result = new Project(project);
            if (result.TargetDir == null || result.AssemblyPath == null)
            {
                result = new GenericUnsupportedProject(project);
            }

            return result;
        }

        public void Save()
        {
            project.Save();
        }

        public bool Build()
        {
            var builder = project.DTE.Solution.SolutionBuild;
            builder.BuildProject(builder.ActiveConfiguration.Name, project.UniqueName, true);
            return builder.LastBuildInfo == 0;
        }

        public void AddFromFile(string path)
        {
            project.ProjectItems.AddFromFile(path);
        }

        public IProjectItem AddFromDirectory(string path)
        {
            return new ProjectItem(project.ProjectItems.AddFromDirectory(path));
        }

        public string GetFilePathFromTypeName(string typeName)
        {
            var codeType = GetCodeTypeFromTypeName(typeName);
            var item = (codeType != null) ? codeType.ProjectItem : null;
            return (item != null) ? item.FileNames[0] : null;
        }

        private CodeType GetCodeTypeFromTypeName(string typeName)
        {
            if (typeName == null || project.CodeModel == null)
            {
                return null;
            }

            var name = ConvertTypeName(project.CodeModel.Language, typeName);
            try
            {
                return project.CodeModel.CodeTypeFromFullName(name);
            }
            catch (ArgumentException ex)
            {
                Logger.Error(ex, "Failed to resolve the code type from full name (language = '{0}', type name = '{1}').", project.CodeModel.Language, name);
                return null;
            }
        }

        private static string ConvertTypeName(string language, string typeName)
        {
            return typeName;
        }
    }
}
